package com.aelitis.azureus.core.peermanager.control.impl;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.disk.DiskManager;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.SystemTime;

import com.aelitis.azureus.core.peermanager.control.SpeedTokenDispenser;
import com.aelitis.azureus.core.util.FeatureAvailability;

public class SpeedTokenDispenserPrioritised implements SpeedTokenDispenser {
    // crude TBF implementation
    private int rateKiB;
    {
        COConfigurationManager.addAndFireParameterListeners(new String[] { "Max Download Speed KBs", "Use Request Limiting" },
                new ParameterListener() {
                    public void parameterChanged(String parameterName) {
                        rateKiB = COConfigurationManager.getIntParameter("Max Download Speed KBs");
                        if (!COConfigurationManager.getBooleanParameter("Use Request Limiting") || !FeatureAvailability.isRequestLimitingEnabled())
                            rateKiB = 0;

                        // sanity check
                        if (rateKiB < 0) {
                            rateKiB = 0;
                        }

                        threshold = Math.max(BUCKET_THRESHOLD_FACTOR * rateKiB, BUCKET_THRESHOLD_LOWER_BOUND);
                        lastTime = currentTime - 1; // shortest possible delta
                        refill(); // cap buffer to threshold in case something accumulated
                    }
                });
    }
    private long threshold;
    private long bucket = 0;
    private long lastTime = SystemTime.getCurrentTime();
    private long currentTime;

    public void update(long newTime) {
        currentTime = newTime;
    }

    // allow at least 2 outstanding requests
    private static final int BUCKET_THRESHOLD_LOWER_BOUND = 2 * DiskManager.BLOCK_SIZE;
    // time (in seconds) at max speed until the buffer is empty: too low = latency issues; too high = overshooting for too long
    private static final int BUCKET_RESPONSE_TIME = 1;
    // n KiB buffer per 1KiB/s speed, that should be roughly n seconds max response time
    private static final int BUCKET_THRESHOLD_FACTOR = 1024 * BUCKET_RESPONSE_TIME;

    public void refill() {
        if (lastTime == currentTime || rateKiB == 0)
            return;

        if (lastTime > currentTime) {
            lastTime = currentTime;
            return;
        }

        if (bucket < 0) {
            Debug.out("Bucket is more than empty! - " + bucket);
            bucket = 0;
        }
        long delta = currentTime - lastTime;
        lastTime = currentTime;
        // upcast to long since we might exceed int-max when rate and delta are
        // large enough; then downcast again...
        long tickDelta = (rateKiB * 1024L * delta) / 1000;
        // System.out.println("threshold:" + threshold + " update: " + bucket + " time delta:" + delta);
        bucket += tickDelta;
        if (bucket > threshold)
            bucket = threshold;
    }

    public int dispense(int numberOfChunks, int chunkSize) {
        if (rateKiB == 0)
            return numberOfChunks;
        if (chunkSize > bucket)
            return 0;
        if (chunkSize * numberOfChunks <= bucket) {
            bucket -= chunkSize * numberOfChunks;
            return numberOfChunks;
        }
        int availableChunks = (int) (bucket / chunkSize);
        bucket -= chunkSize * availableChunks;
        return availableChunks;
    }

    public void returnUnusedChunks(int unused, int chunkSize) {
        bucket += unused * chunkSize;
    }

    public int peek(int chunkSize) {
        if (rateKiB != 0)
            return (int) (bucket / chunkSize);
        else
            return Integer.MAX_VALUE;
    }
}
